package org.egedede.processmanager;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.egedede.processmanager.events.ProcessEvent;
import org.egedede.processmanager.events.ProcessListener;
import org.egedede.processmanager.impl.InputStreamReaderThread;
import org.egedede.processmanager.logs.ProcessLog;
import org.egedede.utils.State;
import org.joda.time.DateTime;

public class SingleProcess {

	private List<ProcessListener> listeners = new LinkedList<ProcessListener>();

	private final String id;
	private final String label;
	private final String command;
	private final ProcessLog log;
	private Process process;

	private DateTime start;
	private DateTime end;

	private final State<Boolean> finished = new State<Boolean>(Boolean.FALSE);

	private final String workingDir;

	private final Map<String, String> env;

	public SingleProcess(String id, String label, String command, String workingDir, Map<String, String> env) {
		this(id, label, command, workingDir, env, Collections.<ProcessListener> emptyList());
	}

	public SingleProcess(String id, String label, String command, String workingDir, Map<String, String> env,
			List<ProcessListener> listeners) {
		this.listeners.addAll(listeners);

		this.id = id;
		this.label = label;
		this.workingDir = workingDir;
		this.env = env;

		this.log = new ProcessLog();
		this.command = command;

	}

	public void start() {
		this.start(false);
	}

	public void start(boolean wait) {
		ProcessBuilder builder = new ProcessBuilder("cmd", "/C", "\"" + this.command + "\"");
		if (this.env != null) {
			builder.environment().putAll(this.env);
		}
		if (this.workingDir != null) {
			builder.directory(new File(this.workingDir));
		}
		try {
			this.process = builder.start();
		} catch (IOException e1) {
			e1.printStackTrace();
			return;
		}
		new Thread(new InputStreamReaderThread(this.process.getInputStream(), this.log, "INFO")).start();
		new Thread(new InputStreamReaderThread(this.process.getErrorStream(), this.log, "ERROR")).start();
		this.start = new DateTime();
		this.fireEvent(new ProcessEvent(ProcessEvent.STARTED, this));

		Runnable runnable = new Runnable() {
			@Override
			public void run() {
				try {
					SingleProcess.this.process.waitFor();
				} catch (Exception e) {
					e.printStackTrace();
				}
				SingleProcess.this.end = new DateTime();
				SingleProcess.this.finished.set(Boolean.TRUE);
				SingleProcess.this.fireEvent(new ProcessEvent(ProcessEvent.FINISHED, SingleProcess.this));
			}
		};
		if (wait) {
			runnable.run();
		} else {
			new Thread(runnable).start();
		}
	}

	public String getId() {
		return this.id;
	}

	public String getLabel() {
		return this.label;
	}

	public String getCommand() {
		return this.command;
	}

	public ProcessLog getLog() {
		return this.log;
	}

	public Process getProcess() {
		return this.process;
	}

	public int exitValue() {
		int result = -1000;
		if (this.isFinished()) {
			result = this.process.exitValue();
		}
		return result;
	}

	public boolean isFinished() {
		return this.finished.get();
	}

	public DateTime getStart() {
		return this.start;
	}

	public DateTime getEnd() {
		return this.end;
	}

	private void fireEvent(ProcessEvent event) {
		for (ProcessListener listener : this.listeners) {
			listener.processChanged(event);
		}
	}

	public void addListener(ProcessListener listener) {
		this.listeners.add(listener);
	}

	public void removeListener(ProcessListener listener) {
		this.listeners.remove(listener);
	}
}